package mosquito.g7;

import java.awt.geom.Line2D;
import java.util.Calendar;
import java.util.Set;

import org.apache.log4j.Logger;

import mosquito.sim.Collector;
import mosquito.sim.GameListener;
import mosquito.sim.Light;
import mosquito.sim.Player;

import mosquito.g7.strategy.SimpleStrategy;
import mosquito.g7.strategy.Strategy;

public class ZapperPlayer extends Player implements GameListener {

    private int numberOfLights;
    private Set<Line2D> walls;
    private Strategy strategy;
    private Board board;

    private static int curNumCenterLights, curNumOtherLights, curOffsetForLightTiming, curInitialPosition;
    private static double curFlyingBuffer;

    private int bestNumCenterLights, bestNumOtherLights, bestOffsetForLightTiming, bestInitialPosition;
    private double bestFlyingBuffer;
    private int bestNumRounds = StaticZap.NUM_MAX_SIMULATOR_ROUNDS + 1;
    private int lastNumRounds;
    private boolean timeToTakeAverage;
    private boolean worthRunningAgain;

    private static Board staticBoard;

    private static final Logger logger = Logger.getLogger(ZapperPlayer.class);
    @Override
    public void gameUpdated(GameUpdateType type) {
        if(type.equals(GameUpdateType.GAMEOVER)) {
            if(bestNumRounds + 100 == getSimulationRounds())
                worthRunningAgain = false;
            lastNumRounds += getSimulationRounds();
            if(lastNumRounds/StaticZap.NUM_SIMULATOR_TRIALS < bestNumRounds && timeToTakeAverage) {
                bestNumRounds = lastNumRounds/StaticZap.NUM_SIMULATOR_TRIALS;
            }
        }
    }

    @Override
    public String getName() {
        return "ZapperPlayer";
    }

    @Override
    public void startSimulatedGame(Set<Line2D> walls, int NumLights) {
        numberOfLights = NumLights;
        this.walls = walls;
        board = staticBoard;
        board.clearNeverPlaceLightHereMarkers();
        strategy = new SimpleStrategy(board, curNumCenterLights, curNumOtherLights, curOffsetForLightTiming, curFlyingBuffer, curInitialPosition);
    }

    private void findBestConfig(int NumLights) {

        Calendar startTime = Calendar.getInstance();
        Calendar timeOut = Calendar.getInstance();
        timeOut.add(Calendar.MINUTE, 58);

        double[] flyingBuffers = {3, 2, 4, 2.5, 3.5, 2, 5, .5}; //ordered such that we find good times first
        for(double flyingBuffer : flyingBuffers) {
            for(curOffsetForLightTiming = 0; curOffsetForLightTiming < 2; curOffsetForLightTiming++) {
                for(curInitialPosition = 0; curInitialPosition < StaticZap.MAX_NUMBER_START_POSITIONS && curInitialPosition <= board.getWalls().size()-4; curInitialPosition++){     
                    for(curNumCenterLights = 1; curNumCenterLights < NumLights && curNumCenterLights < StaticZap.MAX_CENTER_LIGHTS; curNumCenterLights++) {
                        for(curNumOtherLights = 1; curNumOtherLights < 1+ NumLights - curNumCenterLights && curNumOtherLights < StaticZap.MAX_OTHER_LIGHTS; curNumOtherLights++) {

                            if(Calendar.getInstance().after(timeOut)) {
                                logger.debug("Timing out.");
                                return;
                            }

                            curFlyingBuffer = flyingBuffer;
                            timeToTakeAverage = false;
                            lastNumRounds = 0;
                            worthRunningAgain = true;
                            for(int simTrials = 0; simTrials < StaticZap.NUM_SIMULATOR_TRIALS; simTrials++) {
                                if(simTrials == StaticZap.NUM_SIMULATOR_TRIALS - 1)
                                    timeToTakeAverage = true;
                                if(worthRunningAgain)
                                    this.runSimulation(bestNumRounds + 100, this); //KEEP +100 or DIE
                            }

                            if(lastNumRounds/StaticZap.NUM_SIMULATOR_TRIALS == bestNumRounds && worthRunningAgain) {
                                bestNumCenterLights = curNumCenterLights;
                                bestNumOtherLights = curNumOtherLights;
                                bestOffsetForLightTiming = curOffsetForLightTiming;
                                bestFlyingBuffer = curFlyingBuffer;
                                bestInitialPosition = curInitialPosition;
                            }
                            logger.error(curNumCenterLights + "\t" + curNumOtherLights + "\t" + curOffsetForLightTiming + "\t" + flyingBuffer + "\t" + curInitialPosition + "\t" + lastNumRounds/StaticZap.NUM_SIMULATOR_TRIALS);
                        }
                    }
                }
            }
        }
        logger.error("Minutes taken: " + (Calendar.getInstance().getTimeInMillis() - startTime.getTimeInMillis())/60000);
    }

    @Override
    public void startNewGame(Set<Line2D> walls, int NumLights) {

        if(((StaticZap.MAX_X - StaticZap.MIN_X) % StaticZap.GRANULARITY != 0) || ((StaticZap.MAX_Y - StaticZap.MIN_Y) % StaticZap.GRANULARITY != 0))
            throw new RuntimeException("Granularity is incorrect. Fix NOW.");

        numberOfLights = NumLights;
        this.walls = walls;
        board = new Board(walls);
        staticBoard = board;

        bestNumCenterLights = 0;
        bestNumOtherLights = 0;
        bestNumRounds = StaticZap.NUM_MAX_SIMULATOR_ROUNDS + 1;

        SimpleStrategy.resetStaticComponents();
        findBestConfig(NumLights);

        logger.error("Best: " + bestNumCenterLights + "\t" + bestNumOtherLights + "\t" + bestOffsetForLightTiming + "\t" + bestFlyingBuffer + "\t" + bestNumRounds);
        strategy = new SimpleStrategy(board, bestNumCenterLights, bestNumOtherLights, bestOffsetForLightTiming, bestFlyingBuffer, bestInitialPosition);

        if(bestNumCenterLights == 0 && bestNumOtherLights == 0) {
            logger.debug("Failing out.");
            strategy = new SimpleStrategy(board, 2, 1, 0, 3, 0);
        }

    }

    @Override
    public Set<Light> getLights() {
        return strategy.getLights(numberOfLights);
    }

    @Override
    public Collector getCollector() {
        return strategy.getCollector();
    }

    public int getNumberOfLights() {
        return numberOfLights;
    }

    public Set<Line2D> getWalls() {
        return walls;
    }
}
